#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <stdarg.h>
#include <GL/glew.h>
#include <GL/glut.h>

#include "GameContext.h"

#include "ComponentManager.h"
#include "ComponentManager.cpp"
#include "GamePlay.h"

#include <vector>
using std::vector;

#include "Renderable.h"

#include "ImageLoader.h"
#include "textfile.h"


GameContext* gameContext;


ComponentManager<Renderable>& componentManager = ComponentManager<Renderable>::getInstance();

GLfloat    scale;			/* scaling factor */

GamePlay gamePlay;

GLint loc;
GLuint v,f,f2,p,p2;


/* text: general purpose text routine.  draws a string according to
* format in a stroke font at x, y after scaling it by the scale
* specified (scale is in window-space (lower-left origin) pixels).  
*
* x      - position in x (in window-space)
* y      - position in y (in window-space)
* scale  - scale in pixels
* format - as in printf()
*/
void text(GLuint x, GLuint y, GLfloat scale, char* format, ...)
{
	va_list args;
	char buffer[255], *p;
	GLfloat font_scale = 119.05 + 33.33;

	va_start(args, format);
	vsprintf_s(buffer, format, args);
	va_end(args);

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D(0, glutGet(GLUT_WINDOW_WIDTH), 0, glutGet(GLUT_WINDOW_HEIGHT));

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	glPushAttrib(GL_ENABLE_BIT);
	glDisable(GL_LIGHTING);
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_DEPTH_TEST);
	glTranslatef(x, y, 0.0);

	glScalef(scale/font_scale, scale/font_scale, scale/font_scale);

	for(p = buffer; *p; p++)
		glutStrokeCharacter(GLUT_STROKE_ROMAN, *p);

	glPopAttrib();

	glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}


void initGL(void)
{
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

	glEnable(GL_DEPTH_TEST);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glClearColor(0.8, 0.5, 0.4, 1);
}


void setShaders() {

	char *vs = NULL,*fs = NULL,*fs2 = NULL;

	v = glCreateShader(GL_VERTEX_SHADER);
	f = glCreateShader(GL_FRAGMENT_SHADER);
	f2 = glCreateShader(GL_FRAGMENT_SHADER);

	vs = textFileRead("default.vert");
	fs = textFileRead("default.frag");
	fs2 = textFileRead("texture.frag");

	const char * vv = vs;
	const char * ff = fs;
	const char * ff2 = fs2;

	glShaderSource(v, 1, &vv,NULL);
	glShaderSource(f, 1, &ff,NULL);
	glShaderSource(f2, 1, &ff2,NULL);

	free(vs);free(fs);free(fs2);

	glCompileShader(v);
	glCompileShader(f);
	glCompileShader(f2);

	p = glCreateProgram();
	glAttachShader(p,v);
	glAttachShader(p,f);
	glLinkProgram(p);

	p2 = glCreateProgram();
	glAttachShader(p2,v);
	glAttachShader(p2,f2);
	glLinkProgram(p2);

	glUseProgram(p);

	loc = glGetUniformLocation(p,"time");
}

bool firstReshape = true;

void reshape(int width, int height)
{
	if (height == 0)
		height = 1;

	float ratio =  width * 1.0 / height;

	glMatrixMode(GL_PROJECTION);

	glLoadIdentity();

	glViewport(0, 0, width, height);

	gluPerspective(45, ratio, 1, 100);

	glPushMatrix();

	gluLookAt(0, 10, 7, 0, 0, 0, 0, 0, -1);

	glMatrixMode(GL_MODELVIEW);
 
	if (firstReshape)
	{
		gamePlay.init(width, height);
		firstReshape = false;
	}
}

void alert()
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0, 1, 0, 1, 0, 1);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	glDepthMask(false);

	glBegin(GL_QUADS);
		glColor4f(1, 0, 0, 0.6);
		glVertex3f(0, 0, 0);
		glVertex3f(0, 1, 0);
		glColor4f(1, 0, 0, 0);
		glVertex3f(0.4, 1, 0);
		glVertex3f(0.4, 0, 0);
	glEnd();

	glBegin(GL_QUADS);
		glColor4f(1, 0, 0, 0);
		glVertex3f(0.6, 0, 0);
		glVertex3f(0.6, 1, 0);
		glColor4f(1, 0, 0, 0.6);
		glVertex3f(1, 1, 0);
		glVertex3f(1, 0, 0);
	glEnd();

	glDepthMask(true);

	glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}

bool near = false;

void nearCamera()
{
	glMatrixMode(GL_PROJECTION);

	glPopMatrix();
	glPushMatrix();

	gluLookAt(0, 0, 2, 0, 0, 0, 0, 1, 0);

	glMatrixMode(GL_MODELVIEW);

	near = true;
}

void farCamera()
{
	glMatrixMode(GL_PROJECTION);

	glPopMatrix();
	glPushMatrix();

	gluLookAt(0, 10, 7, 0, 0, 0, 0, 0, -1);

	glMatrixMode(GL_MODELVIEW);

	near = false;
}

GLuint backgroundTextureId = -1;

void drawBackground()
{
	glUseProgram(p2);

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0, 1, 0, 1, 0, 1);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	glDepthMask(false);

	if (backgroundTextureId == -1)
	{
		Image *image = loadBMP("background.bmp");

		glGenTextures(1, &backgroundTextureId);
		glBindTexture(GL_TEXTURE_2D, backgroundTextureId);
 		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image->width, image->height, 0, GL_RGB, GL_UNSIGNED_BYTE, image->pixels);

		delete image;
	}	
	glEnable(GL_FRAGMENT_PROGRAM_ARB);

	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, backgroundTextureId);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	glColor3f(1,0,1);
	glBegin(GL_QUADS);
		glTexCoord2d(0, 0);
		glVertex2d(0, 0);
		glTexCoord2d(1, 0);
		glVertex2d(1, 0);
		glTexCoord2d(1, 1);
		glVertex2d(1, 1);
		glTexCoord2d(0, 1);
		glVertex2d(0, 1);
	glEnd();

	glDisable(GL_TEXTURE_2D);
	glDisable(GL_FRAGMENT_PROGRAM_ARB);

	glDepthMask(true);

	glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);

	glUseProgram(p);
}

void display(void)
{
	static int start, end, last;

	start = glutGet(GLUT_ELAPSED_TIME);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	if (true/*performance*/) {
		glColor3f(1.0, 1.0, 1.0);
		text(5, 5, 20, "%.2f fps", 1.0 / ((end - last) / 1000.0));
		last = start;
	}

	drawBackground();

	glPushMatrix();

	glEnable(GL_LIGHTING);
	glEnable(GL_COLOR_MATERIAL);
	glColor3f(0.5, 0.5, 0.5);

	vector<Renderable>& renderables = componentManager.getComponents();
	vector<Renderable>::iterator iter;
	for (iter = renderables.begin(); iter != renderables.end(); iter++)
	{
		iter->draw();
	}

	glPopMatrix();

	if (near) {
		alert();
	}

	glutSwapBuffers();

	end = glutGet(GLUT_ELAPSED_TIME);
}

void idle(void)
{
	glutPostRedisplay();
}

void keyboard(unsigned char key, int x, int y)
{
	switch (key) {
	case 'q':
	case 27:
		exit(0);
	case 'w':
		gamePlay.moveWBC(0,1);
		break;
	case 'a':
		gamePlay.moveWBC(1,0);
		break;
	case 's':
		gamePlay.moveWBC(0,-1);
		break;
	case 'd':
		gamePlay.moveWBC(-1,0);
		break;
	case ' ':
		nearCamera();
		break;
	}

	glutPostRedisplay();
}

void keyboardUp(unsigned char key, int x, int y)
{
	switch (key) {
	case ' ':
		farCamera();
		break;
	}
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
	
	glutInitWindowPosition(100, 50);
	glutInitWindowSize(1200, 700);
	glutCreateWindow("Leukocyte the Doctor");

	glutReshapeFunc(reshape);
	glutDisplayFunc(display);
	glutIdleFunc(idle);
	glutKeyboardFunc(keyboard);
	glutKeyboardUpFunc(keyboardUp);

	glewInit();

	initGL();
	setShaders();

	gameContext = new GameContext();

	glutMainLoop();

	delete gameContext;

	return 0;
}